-
Notifications
You must be signed in to change notification settings - Fork 46
Introduce JUnitClassModifiers
check
#214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
d49b98c
to
3795ab5
Compare
3795ab5
to
b469b47
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit.
We need to think about nested classes. What if that occurs, what are the rules w.r.t. modifiers. Additionally, we need a test case for that 😉.
Maybe we could to a test run on an internal codebase to see how it performs 😄.
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Outdated
Show resolved
Hide resolved
...r-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitMethodDeclaration.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StaticImport.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java
Outdated
Show resolved
Hide resolved
329300e
to
2a995ad
Compare
2a995ad
to
2ee79fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit.
Also asked a few questions :).
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassDeclaration.java
Show resolved
Hide resolved
"", | ||
"// BUG: Diagnostic contains:", | ||
"class B {", | ||
" @ParameterizedTest", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a specific reason to use @ParameterizedTest
? To also account for that case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we check whether the class contains methods annotated with @Test
or with meta annotation org.junit.jupiter.api.TestTemplate
. @ParameterizedTest
is annotated with @TestTemplate
and this test ensures we also match tests like this.
JUnitClassDeclaration
JUnitClassModifiers
check
e9f8a42
to
d2b4225
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit.
I think this is a really nice improvement. Would love to see this applied on our internal codebase :).
I found some edge cases that we should consider:
- If something is abstract, it cannot be final. So we need to exclude that and add a test case for that :).
- In our shared modules we have some
public abstract
classes with tests that can be used by downstream projects. These classes are not located in/src/test/
and markedpublic
on purpose. I think this use case is rather specific to our shared modules repository, but IMO we should do something with it. On the other hand, I'm not sure if looking at the file's location is something we should add as heuristic for flagging these things.
tags = FRAGILE_CODE) | ||
public final class JUnitClassModifiers extends BugChecker implements ClassTreeMatcher { | ||
private static final long serialVersionUID = 1L; | ||
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are re-using some of those, should we maybe move them to MoreMatchers
? I discussed this with @eric-picnic already and he made a setup in #319. Although there we have a specific JUnit
util.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can for now roll with this and have the bigger discussion in #319 as we have some more things that we plan to extract to utils there. So I'd say let's keep this here for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a https://github.com/google/error-prone/blob/master/check_api/src/main/java/com/google/errorprone/matchers/JUnitMatchers.java that sadly enough only supports JUnit lower than 5... Ideally we contribute improvements upstream, but I'm not sure that will work...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought :).
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java
Outdated
Show resolved
Hide resolved
1c9cc15
to
c6e7dab
Compare
Our |
c6e7dab
to
7b99d50
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted the introduction of MoreMatchers
as this will be part of #335.
This means we'll temporarily introduce two duplicate methods hasMetaAnnotation
and hasAnnotation
. However, they will soon be de-duplicated by #335.
I seems better to focus this PR solely on introducing the JUnitClassModifiers
check.
Suggested commit message (repeating PR title):
Introduce `JUnitClassModifiers` check (#214)
Really cool check @oxkitsune , this will have many impact on other teams 🚀 !
allOf( | ||
not(hasMetaAnnotation("org.springframework.context.annotation.Configuration")), | ||
not( | ||
hasMetaAnnotation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can combine these three not(hasMetaAnnotation(
by wrapping them in a not(anyOf(
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One open question from my side left.
linkType = CUSTOM, | ||
link = BUG_PATTERNS_BASE_URL + "JUnitClassDeclaration", | ||
severity = WARNING, | ||
tags = FRAGILE_CODE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tags = FRAGILE_CODE) | |
tags = SIMPLIFICATION) |
Maybe this would fit better? Or STYLE
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushing something as it certainly does not match the description of FRAGILE_CODE
.
9f1e0be
to
a512faa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-approving the latest changes.
Have one thing to ponder, in RefasterRuleModifiersTest
we give more descriptive names to the classes. This makes it easier to see what exact case is being tested. We could decide to do the same here. Already liking the existing setup, so approving regardless.
a512faa
to
ba9c96b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased and added a commit. Only open point from my side is the @Configuration
case. Feel free to send me example classes over Slack.
Updated the suggested commit message.
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java
Outdated
Show resolved
Hide resolved
not( | ||
annotations( | ||
AT_LEAST_ONE, | ||
anyOf( | ||
isType("org.springframework.context.annotation.Configuration"), | ||
hasMetaAnnotation("org.springframework.context.annotation.Configuration")))), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we document for which specific cases we need this? (And does this perhaps only prevent the annotated class from being declared final
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any springboot tests using an annotation marked with @Configuration
cannot be marked final. Usually these classes can be package private, so I'm in favour of doing that.
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java
Outdated
Show resolved
Hide resolved
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java
Outdated
Show resolved
Hide resolved
...-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiersTest.java
Outdated
Show resolved
Hide resolved
...-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiersTest.java
Outdated
Show resolved
Hide resolved
...-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiersTest.java
Outdated
Show resolved
Hide resolved
" @Nested", | ||
" // BUG: Diagnostic contains:", | ||
" class Nested1 {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @Nested
doesn't influence the checker one way or another 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought in this case it could be seen as "more realistic"? As the docs of this annotation mentions that it can be used in these kind of cases. OTOH it could also be deleted indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that realism is better, but (IIUC) there's no code in the checker that could possibly be influenced by the annotation. I'm not sure where exactly we should draw the line, though simpler seems preferred 🤔
@BugPattern( | ||
summary = "JUnit test classes should be declared as package private final", | ||
linkType = CUSTOM, | ||
link = BUG_PATTERNS_BASE_URL + "JUnitClassDeclaration", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
link = BUG_PATTERNS_BASE_URL + "JUnitClassDeclaration", | |
link = BUG_PATTERNS_BASE_URL + "JUnitClassModifiers", |
8b01f2f
to
0efa54b
Compare
Looks good. All 8 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
1 similar comment
Looks good. All 8 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
Looks good. All 9 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get this one out 😄.
return Description.NO_MATCH; | ||
} | ||
|
||
boolean hasSpringConfiguration = TEST_CLASS_WITH_SPRING_CONFIGURATION.matches(tree, state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the code. Got one minor point to consider, do we want to name it TEST_CLASS_WITH_SPRING_CONFIGURATION_ANNOTATION
? It's a bit long I'd say but WITH_SPRING_CONFIGURATION
out of context is not that clear.
4719126
to
10f0900
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased and added one more commit. Repeating the suggested commit message:
Introduce `JUnitClassModifiers` check (#214)
boolean hasSpringConfiguration = TEST_CLASS_WITH_SPRING_CONFIGURATION.matches(tree, state); | ||
if (hasSpringConfiguration && tree.getModifiers().getFlags().isEmpty()) { | ||
return Description.NO_MATCH; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test can be pushed into the TEST_CLASS_WITH_INCORRECT_MODIFIERS
matcher; will push a proposal. (Downside is that the matcher would be evaluated twice in case of a hit, but that should rarely happen, since we expect users to clean up their code ;). Plus, it's not an expensive check.)
hasModifier(Modifier.PROTECTED), | ||
hasModifier(Modifier.PUBLIC))); | ||
|
||
private static final MultiMatcher<ClassTree, AnnotationTree> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static final MultiMatcher<ClassTree, AnnotationTree> | |
private static final Matcher<ClassTree> |
Would also omit the empty line above.
private static final Matcher<ClassTree> TEST_CLASS_WITH_INCORRECT_MODIFIERS = | ||
allOf( | ||
hasMethod(TEST_METHOD), | ||
not(hasModifier(Modifier.ABSTRACT)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this case down so that the modifier ordering here matches the resolution order below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ I'll shuffle the test code accordingly.
if (!hasSpringConfiguration) { | ||
SuggestedFixes.addModifiers(tree, state, Modifier.FINAL).ifPresent(fixBuilder::merge); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pitest doesn't flag it because we're not using the STRONGER
mutators group (I'll fix that), but right now the tests also pass if this suggested fix is applied unconditionally. We need to extend the replacement
test as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix that
I filed #383.
Looks good. All 6 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rule itself LGTM, but I have one more question on one more of the test cases.
I'm optimistically approving however.
" // BUG: Diagnostic contains:", | ||
" class NonFinal {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" class NonFinalWithCustomTestMethod {", | ||
" @ParameterizedTest", | ||
" void foo() {}", | ||
" }", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like these two cases are misleading: IIUC, the latter doesn't fail due to the "custom test method" but solely because it's non-final. Did I get this right?
Shouldn't we add a positive test case for test methods annotated with @ParameterizedTest
? Though one could argue this is already covered by the test cases for MoreJUnitMatchers#TEST_METHOD
in MoreJUnitMatchersTest
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did I get this right?
Yes :)
Shouldn't we add a positive test case for test methods annotated with
@ParameterizedTest
?
Can do :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tnx for the review @werli! Added one more commit.
" // BUG: Diagnostic contains:", | ||
" class NonFinal {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" class NonFinalWithCustomTestMethod {", | ||
" @ParameterizedTest", | ||
" void foo() {}", | ||
" }", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did I get this right?
Yes :)
Shouldn't we add a positive test case for test methods annotated with
@ParameterizedTest
?
Can do :)
Looks good. All 6 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
Nice reviews and commits 🚀 ! |
Implement BugPattern to ensure JUnit test classes are declared as package private final.
Example:
Suggested commit message: